/*
 * Copyright 2014 The Netty Project
 *
 * The Netty Project licenses this file to you under the Apache License,
 * version 2.0 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at:
 *
 *   https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */
package xin.alum.aim.client.websocket;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.DefaultHttpHeaders;
import io.netty.handler.codec.http.HttpClientCodec;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.websocketx.*;
import io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketClientCompressionHandler;
import io.netty.handler.codec.protobuf.ProtobufDecoder;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import io.netty.handler.timeout.IdleStateHandler;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
import xin.alum.aim.coder.WebEncoder;
import xin.alum.aim.constant.AimAttr;
import xin.alum.aim.model.Aim;
import xin.alum.aim.model.Events;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URI;
import java.net.URISyntaxException;

/**
 * This is an example of a WebSocket client.
 * <p>
 * In order to run this example you need a compatible WebSocket server.
 * Therefore you can either start the WebSocket server from the examples
 * or connect to an existing WebSocket server such as
 * <a href="https://www.websocket.org/echo.html">ws://echo.websocket.org</a>.
 * <p>
 * The client will attempt to connect to the URI passed to it as the first argument.
 * You don't have to specify any arguments if you want to connect to the example WebSocket server,
 * as this is the default.
 */
//@SpringBootApplication
public class WebSocketClient {
    static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketClient.class);
    //    static final String URL = System.getProperty("url", "ws://47.110.54.241:8090/ws?token=A&agreement=Json");
    static final String URL = System.getProperty("url", "ws://127.0.0.1:8090/ws?token=a");
    static EventLoopGroup group = new NioEventLoopGroup();
    static WebSocketClientHandler handler;
    static Channel ch;

    public static void main(String... args) throws Exception {
        try {
            connect();
            BufferedReader console = new BufferedReader(new InputStreamReader(System.in));
            while (true) {
                String msg = console.readLine();
                if (msg == null) {
                    break;
                } else if ("bye".equalsIgnoreCase(msg)) {
                    ch.writeAndFlush(new CloseWebSocketFrame());
                    ch.close();
//                    ch.closeFuture().sync();//并不能关闭连接
                    break;
                } else if ("ping".equalsIgnoreCase(msg)) {
                    PingWebSocketFrame frame = new PingWebSocketFrame(Unpooled.wrappedBuffer(new byte[]{8, 1, 8, 1}));
                    ch.writeAndFlush(frame);
                } else if (msg.startsWith("#")) {
                    Events<Aim.Data> events = new Events<>();
                    events.setKey(ch.id().toString());
                    events.setTimestamp(System.currentTimeMillis());
                    ch.writeAndFlush(events);
                } else {
                    WebSocketClientHandler.msgCount = 0;
                    Events<Aim.Data> events = new Events<>();
                    events.setKey(ch.id().toString());
                    events.setTimestamp(System.currentTimeMillis());
                    Aim.Data.Builder d = Aim.Data.newBuilder();
                    d.putAttrs("msg", msg);
                    events.setData(d.build());
                    ch.writeAndFlush(events);
                }
            }

        } finally {
            group.shutdownGracefully();
        }

    }

    public static void connect() throws InterruptedException, IOException, URISyntaxException {
        URI uri = new URI(URL);
        String scheme = uri.getScheme() == null ? "ws" : uri.getScheme();
        final String host = uri.getHost() == null ? "47.110.54.241" : uri.getHost();
        final int port;
        if (uri.getPort() == -1) {
            if ("ws".equalsIgnoreCase(scheme)) {
                port = 80;
            } else if ("wss".equalsIgnoreCase(scheme)) {
                port = 443;
            } else {
                port = -1;
            }
        } else {
            port = uri.getPort();
        }

        if (!"ws".equalsIgnoreCase(scheme) && !"wss".equalsIgnoreCase(scheme)) {
            System.err.println("Only WS(S) is supported.");
            return;
        }

        final boolean ssl = "wss".equalsIgnoreCase(scheme);
        final SslContext sslCtx;
        if (ssl) {
            sslCtx = SslContextBuilder.forClient().trustManager(InsecureTrustManagerFactory.INSTANCE).build();
        } else {
            sslCtx = null;
        }
        Bootstrap b = new Bootstrap();


        // Connect with V13 (RFC 6455 aka HyBi-17). You can change it to V08 or V00.
        // If you change it to V00, ping is not supported and remember to change
        // HttpResponseDecoder to WebSocketHttpResponseDecoder in the pipeline.
        handler = new WebSocketClientHandler(WebSocketClientHandshakerFactory.newHandshaker(uri, WebSocketVersion.V13, null, true, new DefaultHttpHeaders()));
        group = new NioEventLoopGroup(256);
        b = new Bootstrap();
        b.group(group).channel(NioSocketChannel.class)
                .option(ChannelOption.WRITE_BUFFER_WATER_MARK, new WriteBufferWaterMark(6 * 1024, 32 * 1024))
                .option(ChannelOption.SO_RCVBUF, 1024 * 1024)
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) {
                        ChannelPipeline p = ch.pipeline();
                        if (sslCtx != null) {
                            p.addLast(sslCtx.newHandler(ch.alloc(), host, port));
                        }
                        p.addLast(new HttpClientCodec(),
                                new HttpObjectAggregator(8192),
                                new WebEncoder(),
                                new ProtobufDecoder(Aim.Packet.getDefaultInstance()),
                                new IdleStateHandler(6, 6, 20),
                                WebSocketClientCompressionHandler.INSTANCE, handler);
                    }
                });
        ch = b.connect(uri.getHost(), uri.getPort()).addListener(l -> {
            logger.warn("连接成功：{}", l.isSuccess());
        }).sync().channel();
        ch.attr(AimAttr.AGREEMENT).set(Aim.Agreement.Json);
        handler.handshakeFuture().sync();
    }
}
